#include "DelcomUSB.h"
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <mach/mach_error.h>
#include <unistd.h>

/*
 * IOKit utilities
 */

static int dcPrintf(const char* fmt, ...)
{
	static Boolean sDebug = getenv("DC_DEBUG") != NULL;

	int result = 0;

	if (sDebug) {
		va_list args;

		va_start(args, fmt);
		result = fprintf(stderr, "Delcom (%d): ", getpid());
		result += vfprintf(stderr, fmt, args);
		result += fprintf(stderr, "\n");
		va_end(args);
	}

	return result;
}

static mach_port_t getMasterPort()
{
	static mach_port_t sMasterPort = NULL;
	if (!sMasterPort) {
		kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &sMasterPort);
		if (kr != KERN_SUCCESS) {
			dcPrintf("IOMasterPort failed: 0x%x (%s)", kr, mach_error_string(kr));
		}
	}
	return sMasterPort;
}

static io_service_t findMatchingDevice(int ixDevice, SInt32 idVendor, SInt32 idProduct)
{
	kern_return_t		kr;
    CFMutableDictionaryRef 	matchingDictionary = 0;		// requires <IOKit/IOKitLib.h>
    CFNumberRef			numberRef;
    io_iterator_t 		iterator = 0;
    io_service_t		usbDeviceRef;
    mach_port_t			masterPort = getMasterPort();

	if (masterPort == MACH_PORT_NULL) {
		dcPrintf("findMatchingDevice: no master port - can't continue");
		return (io_service_t) -1;
	}

	// requires <IOKit/usb/IOUSBLib.h>
	matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);	
	if (matchingDictionary == NULL) {
		dcPrintf("USBUtils: could not create matching dictionary\n");
		return (io_service_t) -1;
    }

    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor);
    CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBVendorID), numberRef);
    CFRelease(numberRef);

    numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct);
    CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBProductID), numberRef);
    CFRelease(numberRef);

    kr = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator);
    matchingDictionary = 0;			// this was consumed by the above call

	// skip the ones we don't seek (or error out)
	for (int i = 0;  i < ixDevice;  i++) {
		if (!IOIteratorNext(iterator)) {
			dcPrintf("no more devices at index %d\n", i);
		}
	}

	usbDeviceRef = IOIteratorNext(iterator);
	dcPrintf("Hopefully not a null device at 0x%p\n", (void*)usbDeviceRef);
    
    IOObjectRelease(iterator);
    
    return usbDeviceRef;
}

/*
 * API implementation
 */

UInt32 DCGetAPIVersion()
{
	return eDCAPIVersionOne;
}

struct DelcomBoard {
public:
	DelcomBoard(io_service_t device) {
		fDevice = device;
		fDevInterface = NULL;

		IOCFPlugInInterface** iodev;		// requires <IOKit/IOCFPlugIn.h>
		SInt32 score;
		kern_return_t kr;

		kr = IOCreatePlugInInterfaceForService(fDevice, kIOUSBDeviceUserClientTypeID, 
											   kIOCFPlugInInterfaceID, &iodev, &score);
		
		if (kr == KERN_SUCCESS) {
			kr = (*iodev)->QueryInterface(iodev, 
										  CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), 
										  (void**) &fDevInterface);

			(*iodev)->Release(iodev);				// done with this

			if (kr == KERN_SUCCESS) {
				kr = (*fDevInterface)->USBDeviceOpen(fDevInterface);

				if (kr != KERN_SUCCESS) {
					fDevInterface = NULL;
				}
			}
		}
	}

	virtual ~DelcomBoard() {
			if (fDevInterface) {
				(*fDevInterface)->USBDeviceClose(fDevInterface);
				fDevInterface = NULL;
			}
	}

	IOUSBDeviceInterface** getInterface() {
		return fDevInterface;
	}

	io_service_t getService() {
		return fDevice;
	}
	
	kern_return_t reset() {
		return (*fDevInterface)->ResetDevice(fDevInterface);
	}

	kern_return_t control(UInt8 majorCmd, UInt8 minorCmd, UInt8 msbData, UInt8 lsbData, UInt16 extraLen, void* extraBytes) {
		IOUSBDevRequest req;

		req.bmRequestType	= USBmakebmRequestType(majorCmd == kDCReadCmd? kUSBIn : kUSBOut, kUSBVendor, kDelcomUSBDeviceRecipient);
		req.bRequest 		= kDelcomUSBDeviceModel;
		req.wValue 			= (minorCmd << 8) | majorCmd;
		req.wIndex 			= (msbData << 8) | lsbData;
		req.wLength 		= extraLen;
		req.pData 			= extraBytes;
		req.wLenDone 		= 0;
		
		return (*fDevInterface)->DeviceRequest(fDevInterface, &req);
	}

	io_service_t fDevice;
	IOUSBDeviceInterface** fDevInterface;
};

DCBoardRef DCFindBoardByProductID(int ixBoard, UInt32 productID)
{
	DCBoardRef result = NULL;
	io_service_t device = findMatchingDevice(ixBoard, eDC_VendorID, productID);
	if (device > 0) {
		DelcomBoard* board = new DelcomBoard(device);
		if (board->getInterface() == NULL)
			delete board;
		else
			result = board;
	}
	return result;
}

DCBoardRef DCFindBoard(int ixBoard)
{
	return DCFindBoardByProductID(ixBoard, eDC_ProductID);
}

void DCReleaseBoard(DCBoardRef ref)
{
	if (ref)
			delete ref;
}

kern_return_t DCResetBoard(DCBoardRef ref)
{
	return ref->reset();
}

kern_return_t DCControl(DCBoardRef ref, UInt8 majorCmd, UInt8 minorCmd, UInt8 msbData, UInt8 lsbData, UInt16 extraLen, void* extraBytes)
{
	return ref->control(majorCmd, minorCmd, msbData, lsbData, extraLen, extraBytes);
}

kern_return_t DCReadInputs(DCBoardRef ref, UInt8* port0)
{
	UInt8 buffer[8];
	kern_return_t kr = ref->control(kDCReadCmd, kDCReadBothPorts, 0, 0, sizeof(buffer), buffer);
	if (kr == KERN_SUCCESS)
		*port0 = buffer[0];
	return kr;
}

kern_return_t DCWriteOutputs(DCBoardRef ref, UInt8 port1)
{
	return ref->control(kDCWriteCmd, kDCWritePort1, 0, port1, 0, NULL);
}

typedef struct DCPacketStructure DCPacketStructure; 

kern_return_t DCPacketControl(DCBoardRef ref, DCPacketStructure* ioStructure, void* ioBuffer)
{
	return ref->control(ioStructure->MajorCmd, ioStructure->MinorCmd, ioStructure->DataMSB, ioStructure->DataLSB, ioStructure->Length, ioBuffer);
}

io_service_t DCGetDeviceService(DCBoardRef ref)
{
	return ref->getService();
}

IOUSBDeviceInterface** DCGetDeviceInterface(DCBoardRef ref)
{
	return ref->getInterface();
}

